using System;
using System.Threading;
using System.Collections.Generic;

namespace OpenRay
{
	public class OpenGLCamera : Camera
	{
		private Matrix cameraToWorld;
		
		private Point p03, p13;
		private Vector vbx, vby, vfx, vfy;
		
		public OpenGLCamera (IScene s, Matrix c2w, int x, int y)
			: base (s, x, y)
		{
			cameraToWorld = c2w;
			
			Point p00 = cameraToWorld * new Point( -1f, -1f, -1f, 1f );
			//Point p01 = cameraToWorld * new Point( 1f, -1f, -1f, 1f );
			Point p02 = cameraToWorld * new Point( 1f, 1f, -1f, 1f );
			p03 = cameraToWorld * new Point( -1f, 1f, -1f, 1f );
			
			Point p10 = cameraToWorld * new Point( -1f, -1f, 1f, 1f );
			//Point p11 = cameraToWorld * new Point( 1f, -1f, 1f, 1f );
			Point p12 = cameraToWorld * new Point( 1f, 1f, 1f, 1f );
			p13 = cameraToWorld * new Point( -1f, 1f, 1f, 1f );
			
			float divx = 1f / (float)frame.Width;
			float divy = 1f / (float)frame.Height;
			
			vbx = ( p02 - p03 ) * divx;
			vby = ( p00 - p03 ) * divy;
			
			vfx = ( p12 - p13 ) * divx;
			vfy = ( p10 - p13 ) * divy;
		}
		
		public override FrameBuffer Render()
		{			
			Color black = new Color( 0, 0, 0, 1 );
			frame.FillArgb( black.ToArgb() );
			
			ThreadObject[] o = new ThreadObject[threads];
			Thread[] t = new Thread[threads];
			
			int temp = frame.Height / threads;
			int temp2 = frame.Height % threads;
			
			int min = 0;
			
			for ( int i = 0; i < threads; i++ )
			{
				int max = min + temp;
				
				if ( temp2 > 0 )
				{
					max++;
					temp2--;
				}
				o[i] = new ThreadObject( frame, min, max, maxDepth, frame.Width, vby, vfy, vbx, vfx, p03, p13, scene );
				t[i] = new Thread( new ThreadStart( o[i].renderRows ) );
				t[i].Start();
				
				min = max;
			}
			
			for ( int i = 0; i < threads; i++ )
			{
				t[i].Join();
			}
			
			return frame;
		}
	}
	
	public class ThreadObject
	{
		private FrameBuffer fb;
		private int min, max;
		private uint maxDepth;
		private int xRes;
		
		private Vector vby, vfy, vbx, vfx;
		private Point p03, p13;
		private IScene scene;
		
		public ThreadObject( FrameBuffer fba, int mina, int maxa, uint md, int xr, Vector vbya, Vector vfya, Vector vbxa, Vector vfxa, Point p03a, Point p13a, IScene s )
		{
			fb = fba;
			min = mina;
			max = maxa;
			maxDepth = md;
			xRes = xr;
			vby = vbya;
			vfy = vfya;
			vbx = vbxa;
			vfx = vfxa;
			p03 = p03a;
			p13 = p13a;
			scene = s;
		}
		
		public void renderRows()
		{
			for ( int y = min; y < max; y++ )
			{
				Vector sby = vby * ((float)y + 0.5f);
				Vector sfy = vfy * ((float)y + 0.5f);
				
				for ( int x = 0; x < xRes; x++ )
				{
					Vector sbx = vbx * ((float)x + 0.5f);
					Vector sfx = vfx * ((float)x + 0.5f);
					
					Point back = p03 + sbx + sby;
					Point front = p13 + sfx + sfy;
					
					Ray r = new Ray( back, front - back );
					IList<PrimitiveHit> hits = scene.Trace(r, maxDepth);
					
					if ( hits.Count > 0 )
					{
						fb.SetPixelArgb( x, y, hits[0].Shade(r).ToArgb() );
					}
				}
			}
		}
	}
}

